﻿/* 
 *  <copyright file="User2DPlatform.cs" company="Wesley Jowitt">
 *  
 *  SobrietyEngine: Copyright (c) 2011 Wesley Jowitt
 *  
 *  This software is distributed under a software license.
 *   * As of the creation time of this file, the license in use is the Microsoft Permissive License (Ms-PL) 
 *    * The license can be viewed at:
 *     * License.Txt in the root folder of this project
 *      * http://sobrietyengine.codeplex.com/license
 *       * http://www.microsoft.com/opensource/licenses.mspx
 *       
 *  </copyright>
 *   */

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using SEGameExample.Events.UserActions;
using SobrietyEngine.Common;
using SobrietyEngine.Components;
using SobrietyEngine.Components.Controller;
using SobrietyEngine.Components.Physics.OnCollision;
using SobrietyEngine.Events;
using SobrietyEngine.Events.UserActions;
using SobrietyEngine.Guids;
using SobrietyEngine.Logging;

namespace SEGameExample.Components.Controllers
{
    class User2DPlatform : ControllerCmp, IComponent, IEventListener
    {
      
        IPosition PositionComp = null;
        Vector3 Move = Vector3.Zero;

        IOrientation RotationComp = null;
        Vector3 Rotate = Vector3.Zero;

        CGuid OwningUser { get; set; }

        IFarseerPhysicsBody PhysicsComp = null;

        OnGroundCmp onGroundComp = null;

        Boolean jump = false;
        Boolean superJump = false;
        float jumpAccel = 8.0f;
        float superJumpMultiplier = 1.5f;

        float maxWalkVel = 3.0f;
        float walkAccel = 1.0f;

        public User2DPlatform(CGuid userGuid)
            : base()
        {
            OwningUser = userGuid;
        }

        override public void Set(Parameters parms)
        {
            jumpAccel = parms.GetValueAs<float>("JumpAccel", 8.0f, true);
            walkAccel = parms.GetValueAs<float>("WalkAccel", 1.0f, true);
            maxWalkVel = parms.GetValueAs<float>("MaxWalkVel", 8.0f, true);
        }

        public override List<ParameterDescription> GetParameterDescriptions()
        {
            List<ParameterDescription> d = new List<ParameterDescription>();
            d.Add(new ParameterDescription("JumpAccel", "Jump Accelleration in m/s", typeof(float), 8f));
            d.Add(new ParameterDescription("MaxWalkVel", "Maximum waling velocity in m/s", typeof(float), 3f));
            d.Add(new ParameterDescription("WalkAccel", "Accelleration of walking in m/s", typeof(float), 1f));
            return d;
        }

        override public Type[] SupportedInterfaces()
        {
            return new Type[] { typeof(IController) };
        }

        override public Boolean InitComponent()
        {
            //get our components
            PositionComp = Actor.Get<IPosition>();
            RotationComp = Actor.Get<IOrientation>();
            PhysicsComp = Actor.Get<IFarseerPhysicsBody>();
            onGroundComp = Actor.Get<OnGroundCmp>();
   
            //check our components, make sure they are what we expect
            if (PositionComp == null || RotationComp == null||
                PhysicsComp == null || onGroundComp == null)
            {
                Logger.Debug(this, "Missing component: delaying init");
                return false;
            }


            //add controller listener events
            RegisterEventListeners();

            return true;
        }

        public virtual void RegisterEventListeners()
        {
            Actor.EventRouter.AddEventListener(typeof(Move), this);
            Actor.EventRouter.AddEventListener(typeof(Rotate), this);
            Actor.EventRouter.AddEventListener(typeof(Jump), this);
            Actor.EventRouter.AddEventListener(typeof(SuperJump), this);
            Actor.EventRouter.AddEventListener(typeof(Use), this);
        }

        public new void Remove()
        {
            //remove controller listener events
            UnregisterEventListeners();
        }

        public virtual void UnregisterEventListeners()
        {
            Actor.EventRouter.RemoveEventListener(this);
        }

        public void HandleEvent(Event e)
        {
            //not enabled, exit out
            if (!Enabled)
                return;

            //if this event is not from the owning player
            if(OwningUser != e.From)
                return;

            //handle our move event
            if (e.Type == typeof(Move))
            {
                Move m = (Move)e.Data;
                Move = m.MoveBy;
            }

            //handle our rotate event
            if (e.Type == typeof(Rotate))
            {
                Rotate r = (Rotate)e.Data;
                Rotate = r.RotateBy;
            }

            //handlejump
            if (e.Type == typeof(Jump))
            {
                jump = true;
            }

            //handle super jump
            if (e.Type == typeof(SEGameExample.Events.UserActions.SuperJump))
            {
                jump = true;
                superJump = true;
            }
        }

        public new void Update(GameTime ElapsedTime)
        {
            //LoggerGameService.GetSingleton().WriteLine(Move.X.ToString() + " - " + Move.Y.ToString(), LogMessageTypes.Debugging);
            //Logger.GetSingleton().WriteLine(PositionComp.Get().X.ToString() + " - " + PositionComp.Get().Y.ToString(), LogMessageTypes.Debugging);
            //LoggerGameService.GetSingleton().WriteLine(Rotate.X.ToString() + " - " + Rotate.Y.ToString(), LogMessageTypes.Debugging);

            if (PhysicsComp.Body != null)
            {
                //need walk and jump
                //walking is applying force until it hits max force
                //0 means slow the player down till he stops instead
                //jumping is instantaneous force upwards
                
                //set some physics constants on the body
                PhysicsComp.Body.AngularDamping = 20.0f;
                PhysicsComp.Body.Friction = 10.0f;

                if (Move != Vector3.Zero)
                {
                    //we want to get rid of any move velocity that is perpendicular to ground
                    //for now, just get rid of Y component of move. lazy, i know
                    Vector2 adjMove = new Vector2(Move.X, Move.Y);
                    adjMove.Y = 0;

                    Vector2 walkForce = Vector2.Zero;

                    Vector2 linearVelocity = PhysicsComp.Body.LinearVelocity;

                    float dot = Vector2.Dot(linearVelocity, adjMove);
                    float linearVelocityLen = linearVelocity.Length();
                    float velDiff = maxWalkVel - linearVelocityLen;

                    //if PlayerOne is pressing same dir actor is already heading
                    if (dot > 0 && velDiff > 0)
                    {
                        //we can apply a bt of force, not to exceed our max velocity
                        //direction vector * difference in velocity
                        walkForce = adjMove * (velDiff * PhysicsComp.Body.Mass);
                    }
                    //if PlayerOne is pushing opposite direction
                    else if (dot < 0)
                    {
                        walkForce = adjMove * (walkAccel * PhysicsComp.Body.Mass);
                    }
                    //perpendicular
                    else if (dot == 0 && linearVelocityLen == 0)//i dot == 0
                    {
                        //body not moving hits this case
                        walkForce = adjMove * (walkAccel * PhysicsComp.Body.Mass);
                    }
                    else if (dot == 0 && linearVelocityLen != 0)
                    {
                        //maybe look/climb/duck or something
                    }


                    //apply walk 
                    PhysicsComp.Body.ApplyLinearImpulse(walkForce);
                }
               
                //Jump 
                //change this to create a jump direction 
                //that is in the direction of the current colision contact normal
                //and uses the perpendicular portion of velocity
                //may also include moveForce so that immediate joystick input also effects jump
                //until then, a simple upwards force is applied for testing   
                //or use Orientation and an upvector to always know which way th eplayer is jumping
                if (jump)
                {
                    //if we are on the ground...
                    if (onGroundComp.IsOnGround || onGroundComp.TimeOffGroundMs < 20)
                    {
                        //using just avg of contacts & push is ok, bu lets try to angle towards joystick dir
                        //lets lamp the X dir of the mvoe so we dont leap too far left or worldRight
                        Vector2 adjMove = new Vector2(Move.X, Move.Y);
                        adjMove.Y = 0; //no need for Y component

                        if (Math.Abs(adjMove.X) > 0.75)
                            adjMove.X *= (Math.Abs(adjMove.X) - 0.75f);

                        Vector2 groundNormal = onGroundComp.AvgOfContacts;
                        if (float.IsNaN(groundNormal.X) || float.IsNaN(groundNormal.Y) || groundNormal == Vector2.Zero)
                        {
                            groundNormal = onGroundComp.PreviousContactAvg;
                        }
                        Vector2 jumpDir = groundNormal + adjMove;
                        jumpDir.Normalize();
                        Vector2 force = jumpAccel * PhysicsComp.Body.Mass * jumpDir;

                        if (float.IsNaN(force.X) || float.IsNaN(force.Y) || force == Vector2.Zero)
                            return;

                        if (superJump)
                            force *= superJumpMultiplier;

                        PhysicsComp.Body.ApplyLinearImpulse(force);
                    }
                    jump = false;
                    superJump = false;
                }
            }
        }
    }
}